#include "planner/stats_calculator.h"

#include <cstdint>

#include "common/defer.h"
#include "planner/logical_operators.h"
#include "planner/optimizer_context.h"

extern "C" {
#include <access/table.h>
#include <nodes/nodeFuncs.h>
#include <nodes/nodes.h>
#include <nodes/pg_list.h>
#include <optimizer/optimizer.h>
#include <optimizer/plancat.h>
#include <optimizer/tlist.h>
#include <utils/lsyscache.h>
}

namespace optimizer {

void StatsCalculator::CalculateStats(GroupExpression *gexpr, OptimizerContext *context) {
  gexpr_ = gexpr;
  group_ = context->GetMemo().GetGroupByID(gexpr_->GetGroupID());
  context_ = context;
  DriveGroupTarget();
  gexpr->Contents()->Accept(this);
}

void StatsCalculator::DriveGroupTarget() {
  if (group_->GetGroupTarget() != nullptr)
    return;

  auto *tlist = create_empty_pathtarget();

  tlist->cost.startup = 0;
  tlist->cost.per_tuple = 0;

  // TODO: use rel
  Oid reloid = InvalidOid;
  int32_t item_width = 0;
  int64_t tuple_width = 0;

  foreach_node(Node, node, gexpr_->Contents()->GetProject()) {
    if (IsA(node, Var)) {
      const Var *var = (const Var *)node;
      if (reloid != InvalidOid && var->varattno > 0) {
        if (item_width = get_attavgwidth(reloid, var->varattno); item_width > 0) {
          tuple_width += item_width;
          continue;
        }
      }
      tuple_width += get_typavgwidth(var->vartype, var->vartypmod);
    } else {
      tuple_width += get_typavgwidth(exprType(node), exprTypmod(node));
      // cost_qual_eval_node(&cost, node, root);
      // rel->reltarget->cost.startup += cost.startup;
      // rel->reltarget->cost.per_tuple += cost.per_tuple;
    }
    tlist->exprs = lappend(tlist->exprs, node);
  }
  group_->GetGroupStats().tuple_width_ = clamp_width_est(tuple_width);
  group_->SetGroupTarget(tlist);
}

void StatsCalculator::Visit(LogicalGet *logical_get) {
  if (logical_get->GetRangeTblEntry() == nullptr) {
    group_->GetGroupStats().node_rows_ = 1;
    return;
  }

  auto *rte = logical_get->GetRangeTblEntry();

  auto *rel = table_open(rte->relid, NoLock);
  ScopedDefer defer{[] {}, [&]() { table_close(rel, NoLock); }};
  BlockNumber pages;
  Cardinality tuples;
  double allvisfrac;
  estimate_rel_size(rel, nullptr, &pages, &tuples, &allvisfrac);
  group_->GetGroupStats().node_rows_ = clamp_row_est(tuples);
}

}  // namespace optimizer